package util

import (
	"errors"
	"fmt"
	"github.com/nuttech/bell"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"io/fs"
	"os"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"

	corev1 "k8s.io/api/core/v1"
)

type LogStatus string

// todo: set vars private
var (
	KbltMutator                 = os.Getenv("KBLT_MUTATOR")
	KbltValidator               = os.Getenv("KBLT_VALIDATOR")
	KbltMainPort                = os.Getenv("KBLT_PORT_MAIN")
	IKbltMainPort               = 0
	KbltLoggerPort              = os.Getenv("KBLT_PORT_LOG")
	IKbltLoggerPort             = 0
	KbltPrefixEnabled           = os.Getenv("KBLT_ENABLED_TRIGGER_PREFIX")
	KbltLabelException          = os.Getenv("KBLT_PREFIX_EXCEPTION")
	KbltSideEffect              = os.Getenv("KBLT_SIDE_EFFECT")
	KbltLabelExceptionValues    []string
	KbltPermissionsStartOnly    = os.Getenv("KBLT_PERMISSIONS_START_ONLY")
	KbltPermissionsLogOnly      = os.Getenv("KBLT_PERMISSIONS_LOG_ONLY")
	KbltPermissionsCheckPeriod  = os.Getenv("KBLT_PERMISSIONS_CHECK_PERIOD")
	KbltPermissionsCheck        = os.Getenv("KBLT_PERMISSIONS_CHECK")
	IKbltPermissionsCheckPeriod = time.Minute * 3
)

const (
	TemplateTypeConfig = "template"
	TriggerTypeConfig  = "trigger"
	RuleTypeConfig     = "rule"
)

const (
	SuccessStatus LogStatus = "Success"
	FailedStatus  LogStatus = "Failed"
	IgnoreStatus  LogStatus = "Ignored"
)

type ConditionStatus string

const (
	ConditionTrue  ConditionStatus = "True"
	ConditionFalse ConditionStatus = "False"
)

var StatusInfo []Status

type Status struct {
	TypeConfig string          `yaml:"typeConfig"`
	NameConfig string          `yaml:"nameConfig"`
	Status     ConditionStatus `yaml:"status"`
	Message    string          `yaml:"message"`
}

const (
	// TemplateLeftDelimiter is left delimeter for sidecar config
	TemplateLeftDelimiter = "{{%"
	// TemplateRightDelimiter is right delimeter for sidecar config
	TemplateRightDelimiter = "%}}"
)

type RenderItem struct {
	Template             v1alpha1.Template
	Render               string
	Action               string
	PluginName           string
	CreationIsUniqueName bool
}

// GetAnnotation formats a fully qualified annotation  from a prefix and a name.
// For example, with prefix "annotation.io" and name "key", it returns "annotation.io/key".
func GetAnnotation(prefix string, name string) string {
	// TODO validation on prefix/name? K8s has some restrictions on what are valid annotations.
	// See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/.
	return fmt.Sprintf("%s/%s", prefix, name)
}

func GetFunctionName(i interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

// MergeVolumes merges target with added, but only if a Volume does not exist in target.
func MergeVolumes(target, added []corev1.Volume) []corev1.Volume {
	return append(target, DeDuplicateVolumes(target, added)...)
}

// MergeVolumeMounts merges target with added, but only if a Volume does not exist in target.
func MergeVolumeMounts(target, added []corev1.VolumeMount) []corev1.VolumeMount {
	return append(target, DeDuplicateVolumeMounts(target, added)...)
}

// DeDuplicateVolumes returns all or some of added only if they do not already exist in target
func DeDuplicateVolumes(target, added []corev1.Volume) []corev1.Volume {
	var uniqueVolumes []corev1.Volume
	targetNames := map[string]bool{}
	for _, v := range target {
		targetNames[v.Name] = true
	}
	for _, add := range added {
		if _, exists := targetNames[add.Name]; !exists {
			uniqueVolumes = append(uniqueVolumes, add)
		}
	}
	return uniqueVolumes
}

// DeDuplicateVolumes returns all or some of added only if they do not already exist in target
func DeDuplicateContainers(target, added []corev1.Container) []corev1.Container {
	var uniqueContainers []corev1.Container
	targetNames := map[string]bool{}
	for _, v := range target {
		targetNames[v.Name] = true
	}
	for _, add := range added {
		if _, exists := targetNames[add.Name]; !exists {
			uniqueContainers = append(uniqueContainers, add)
		}
	}
	return uniqueContainers
}

// DeDuplicateVolumeMounts returns all or some of added only if they do not already exist in target
func DeDuplicateVolumeMounts(target, added []corev1.VolumeMount) []corev1.VolumeMount {
	var uniqueVolumeMounts []corev1.VolumeMount
	targetNames := map[string]bool{}
	for _, vm := range target {
		targetNames[vm.Name] = true
	}
	for _, add := range added {
		if _, exists := targetNames[add.Name]; !exists {
			uniqueVolumeMounts = append(uniqueVolumeMounts, add)
		}
	}
	return uniqueVolumeMounts
}

func IsMutatorRole() bool {
	return KbltMutator == "true"
}

func IsKbltPermissionsLogOnly() bool {
	return KbltPermissionsLogOnly == "true"
}

func IsKbltPermissionsStartOnly() bool {
	return KbltPermissionsStartOnly == "true"
}

func IsPermissionsCheck() bool {
	return KbltPermissionsCheck == "true"
}

func IsSideEffectRole() bool {
	return KbltSideEffect == "enabled" || KbltSideEffect == "true"
}

func IsValidatorRole() bool {
	return KbltValidator == "true"
}

func IsFullRole() bool {
	if IsMutatorRole() && IsValidatorRole() {
		return true
	}
	return false
}

func CreateDir(dir string) error {
	_, err := os.ReadDir(dir)

	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			err := os.Mkdir(dir, os.FileMode(0755))
			if err != nil {
				return fmt.Errorf("%v", err)
			}
		} else {
			return fmt.Errorf("%v", err)
		}
	}
	return nil
}

func GlobalInit() {
	LogsPath := os.Getenv("LOGS_PATH")
	TmpPath := os.Getenv("TMPDIR")
	KbltMutator = os.Getenv("KBLT_MUTATOR")
	KbltValidator = os.Getenv("KBLT_VALIDATOR")
	KbltMainPort = os.Getenv("KBLT_PORT_MAIN")
	KbltLoggerPort = os.Getenv("KBLT_PORT_LOG")
	KbltLabelException = os.Getenv("KBLT_PREFIX_EXCEPTION")
	KbltSideEffect = os.Getenv("KBLT_SIDE_EFFECT")
	KbltPermissionsCheck = os.Getenv("KBLT_PERMISSIONS_CHECK")
	KbltPermissionsLogOnly = os.Getenv("KBLT_PERMISSIONS_LOG_ONLY")
	KbltPermissionsStartOnly = os.Getenv("KBLT_PERMISSIONS_START_ONLY")
	KbltPermissionsCheckPeriod = os.Getenv("KBLT_PERMISSIONS_CHECK_PERIOD")

	if LogsPath == "" || TmpPath == "" {
		panic("INITIALIZE ERROR: ENV LOGS_PATH, TMPDIR are required")
	}

	var err error

	if LogsPath != "" {
		err = CreateDir(LogsPath)
	}
	if TmpPath != "" {
		err = CreateDir(TmpPath)
	}

	if err != nil {
		panic("INITIALIZE ERROR: " + err.Error())
	}

	if KbltMainPort == "" {
		IKbltMainPort = 9443
	} else {
		IKbltMainPort, _ = strconv.Atoi(KbltMainPort)
	}

	if KbltLoggerPort == "" {
		IKbltLoggerPort = IKbltMainPort + 1
	} else {
		IKbltLoggerPort, _ = strconv.Atoi(KbltLoggerPort)
	}

	if KbltPrefixEnabled == "" {
		KbltPrefixEnabled = "true"
	}

	if KbltLabelException != "false" && KbltLabelException != "" {
		if KbltLabelException == "true" {
			KbltLabelExceptionValues = []string{"istiod"}
		} else {
			KbltLabelExceptionValues = []string{"istiod"}
			KbltLabelExceptionValues = append(KbltLabelExceptionValues, strings.Split(KbltLabelException, ",")...)
		}
	} else {
		KbltLabelExceptionValues = []string{}
	}

	if KbltPermissionsLogOnly == "" {
		KbltPermissionsLogOnly = "false"
	}

	if KbltPermissionsCheckPeriod == "" {
		KbltPermissionsCheckPeriod = "3m"
	} else {
		IKbltPermissionsCheckPeriod, err = time.ParseDuration(KbltPermissionsCheckPeriod)
		if err != nil {
			panic("INITIALIZE ERROR: KBLT_PERMISSIONS_CHECK_PERIOD cannot parse duration:" +
				"  Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
		}
	}

	if KbltPermissionsCheck == "" {
		KbltPermissionsCheck = "true"
	}

	if KbltPermissionsStartOnly == "" {
		KbltPermissionsStartOnly = "false"
	}

}

func MergeSliceWithoutDuplicate(aT []interface{}, aS []interface{}) []interface{} {
	for _, item := range aS {
		found := false
		if reflect.TypeOf(item).Kind() == reflect.Map {
			name, ok := item.(map[string]interface{})["name"]
			if ok {
				for i, origItem := range aT {
					if reflect.TypeOf(origItem).Kind() == reflect.Map {
						origName, ok := origItem.(map[string]interface{})["name"]
						if ok {
							if name == origName {
								found = true
								aT[i] = MergeStruct(origItem.(map[string]interface{}), item.(map[string]interface{}), true)
							}
						}
					}
				}
				if !found {
					aT = append(aT, item)
				}
			} else {
				aT = append(aT, item)
			}
		} else {
			if !contains(aT, item) {
				aT = append(aT, item)
			}
		}
	}

	return aT
}

func contains(s []interface{}, e interface{}) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func MergeSliceCommon(aT []interface{}, aS []interface{}, dupl bool) []interface{} {
	if dupl {
		return MergeSliceWithoutDuplicate(aT, aS)
	} else {
		MergeSlice(aT, aS)
	}
	return nil
}

func MergeSlice(aT []interface{}, aS []interface{}) []interface{} {
	for _, item := range aS {
		found := false
		if reflect.TypeOf(item).Kind() == reflect.Map {
			name, ok := item.(map[string]interface{})["name"]
			if ok {
				for i, origItem := range aT {
					if reflect.TypeOf(origItem).Kind() == reflect.Map {
						origName, ok := origItem.(map[string]interface{})["name"]
						if ok {
							if name == origName {
								found = true
								aT[i] = MergeStruct(origItem.(map[string]interface{}), item.(map[string]interface{}), false)
							}
						}
					}
				}
				if !found {
					aT = append(aT, item)
				}
			} else {
				aT = append(aT, item)
			}
		} else {
			aT = append(aT, item)
		}
	}

	return aT
}

func MergeStruct(aT map[string]interface{}, aS map[string]interface{}, dupl bool) map[string]interface{} {
	for k, item := range aS {
		if reflect.TypeOf(item).Kind() == reflect.Map {
			val, ok := aT[k]
			if ok {
				aT[k] = MergeStruct(val.(map[string]interface{}), item.(map[string]interface{}), dupl)
			} else {
				aT[k] = item
			}
		} else if reflect.TypeOf(item).Kind() == reflect.Slice {
			_, ok := aT[k]
			if ok {
				aT[k] = MergeSliceCommon(aT[k].([]interface{}), item.([]interface{}), dupl)
			} else {
				aT[k] = item
			}
		} else {
			aT[k] = item
		}
	}
	return aT
}

func SendEvent(eventName string, value interface{}) {
	err := bell.Ring(eventName, value)
	if err != nil {
		logs.Errorf("Can't send event %s. Values:%s, Error: %s", eventName, value, err)
	}
}

func SendEventWithErr(eventName string, value interface{}) error {
	err := bell.Ring(eventName, value)
	if err != nil {
		logs.Errorf("Can't send event %s. Error: %s", eventName, err)
	}
	return err
}
